Skip to content

Compute the hover overlay position from live rects (fix first-frame jump)#5126

Merged
lukemelia merged 5 commits into
mainfrom
fix-adorn-label-tab-position-flash
Jun 5, 2026
Merged

Compute the hover overlay position from live rects (fix first-frame jump)#5126
lukemelia merged 5 commits into
mainfrom
fix-adorn-label-tab-position-flash

Conversation

@lukemelia

@lukemelia lukemelia commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Fixes the first-frame jump on the CardsGrid hover chrome — the teal Adorn type-label tab, the select chip, the menu, and the outline all painting ~60px off for one frame then snapping into place.

Root cause (confirmed by instrumenting the velcro offset middleware)

The hover overlay is positioned by velcro (floating-ui). The middleware returned floating-ui's rects.reference, but frame-by-frame capture showed floating-ui's first one-or-two computePosition calls return the reference in viewport coordinates (y=200.4) and only subtract the offset parent's offset (opTop=60) a frame later (y=140.4). Everything else was constant the whole time. So the overlay painted at viewport - 0 for a frame, then corrected to viewport - offsetParent, and everything riding it jumped with it.

Fix

The capture also showed the inputs needed are correct from frame 0: the reference's own getBoundingClientRect and the floating element's offsetParent rect. Compute the position from those directly — (refRect.left − parentRect.left) / scaleX, (refRect.top − parentRect.top) / scaleY, recovering the offset parent's scale the same way the Adorn label positioner does for the scaled test runner — instead of trusting floating-ui's rects.reference. It's correct on the very first frame. No hiding, no rAF, no deferred reveal; the overlay simply lands in the right place immediately.

History

Earlier revisions chased label height, then position: absolute timing, then hiding the overlay until settle (opacity gate / rect-stability watch). The middleware capture disproved each: height was constant, floatPos was already absolute, and hiding fought a rAF-vs-correction race. This addresses the actual returned coordinate.

Notes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Preview deployments

Host Test Results

    1 files      1 suites   1h 48m 15s ⏱️
2 936 tests 2 921 ✅ 15 💤 0 ❌
2 955 runs  2 940 ✅ 15 💤 0 ❌

Results for commit ce1369d.

Realm Server Test Results

    1 files      1 suites   10m 53s ⏱️
1 559 tests 1 558 ✅ 1 💤 0 ❌
1 650 runs  1 649 ✅ 1 💤 0 ❌

Results for commit ce1369d.

@lukemelia lukemelia force-pushed the fix-adorn-label-tab-position-flash branch from 5b6423d to 63412bb Compare June 5, 2026 03:00
@lukemelia lukemelia changed the title Anchor the Adorn label tab by transform to stop the on-hover position flash Hide the Adorn label tab until its position settles (fix on-hover flash) Jun 5, 2026
…rame jump

The hover overlay is positioned by velcro (floating-ui), but it only became
`position: absolute` inside the offset middleware — which runs *after*
floating-ui has already measured the rects on the first computePosition. So
the first frame was measured against the wrong containing block and the whole
overlay (the teal type-label tab, the select chip, the menu, the outline)
painted ~60px off, then snapped into place one frame later.

floating-ui requires the floating element to already be absolutely positioned
when it first measures. Declare `position: absolute` in the overlay's CSS so
it is in effect before velcro's first measurement; the middleware still sets
it too, harmlessly. Confirmed via frame-by-frame capture that the overlay's
own top/left was the thing jumping (526->466 between frame 0 and 1), so fixing
it here fixes every piece of chrome that rides the overlay.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia lukemelia force-pushed the fix-adorn-label-tab-position-flash branch from 63412bb to 561c680 Compare June 5, 2026 03:13
@lukemelia lukemelia changed the title Hide the Adorn label tab until its position settles (fix on-hover flash) Position the hover overlay from CSS to stop the first-frame jump Jun 5, 2026
lukemelia and others added 2 commits June 4, 2026 23:21
Gated debug logging (localStorage['adorn-pos-debug']='1' or ?adornPosDebug=1)
in the offset middleware: per computePosition, logs the returned x/y, the
reference card's rect, the floating element's computed position + offset
parent + that parent's top, and scroll offsets — so we can see which quantity
is ~60px off on the first call. Includes a v-mw1 build marker. Diagnostic
only; keeps the position:absolute CSS so we can also confirm whether it took
effect (floatPos field). Do not merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frame-by-frame capture of the velcro offset middleware showed floating-ui's
first one-or-two computePosition calls return the reference card in viewport
coordinates instead of offset-parent-relative ones — it omits the offset
parent's offset (here 60px) for ~1 frame, then corrects. So the overlay, and
everything riding it (the teal type-label tab, the select chip, the menu, the
outline), painted ~60px off and visibly jumped into place on first hover.

Keep the overlay hidden until its position settles: in the offset middleware,
set opacity 0 and reschedule a reveal one animation frame after the most recent
position write, so it reveals only once movement stops (the wrong value can
repeat across calls, so converging on equality isn't enough). Opacity — not
visibility — so the overlay's action buttons stay hit-testable throughout.
Gated behind `hideUntilPositioned`, which only OperatorModeOverlays (the
overlay with visible chrome) turns on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia lukemelia changed the title Position the hover overlay from CSS to stop the first-frame jump Hide the hover overlay until velcro settles (fix first-frame jump) Jun 5, 2026
lukemelia and others added 2 commits June 4, 2026 23:49
The previous attempt rescheduled the reveal off requestAnimationFrame inside
the offset middleware, but floating-ui's correction to the right position also
lands a frame later, so the reveal could win that race and flash the overlay at
the wrong (first-measure) position before it corrected.

Watch the overlay's actual measured rect instead: hold it at opacity 0 from
before the first paint (in a modifier) and reveal only once getBoundingClientRect
is unchanged for two consecutive frames (with a safety cap). The wrong position
only appears for a single frame, so it can never be the reveal frame, and there
is no dependence on middleware-output equality or frame counts. Opacity, not
visibility, so the overlay's action buttons stay hit-testable. Gated behind
hideUntilPositioned, which only OperatorModeOverlays enables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…jump

The velcro offset middleware returned floating-ui's `rects.reference`, but
frame-by-frame capture showed floating-ui's first one-or-two computePosition
calls return the reference in viewport coordinates and only subtract the offset
parent's offset a frame later — so the overlay (and everything riding it: the
type-label tab, the select chip, the menu, the outline) painted ~60px off and
jumped into place on first hover.

The capture also showed the inputs we need are correct from frame 0: the
reference's own getBoundingClientRect and the floating element's offsetParent
rect. Compute the position from those directly (recovering the offset parent's
scale the same way the Adorn label positioner does, for the scaled test
runner), so it's right on the very first frame. No hiding, no rAF, no deferred
reveal — the overlay simply lands in the correct place immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lukemelia lukemelia changed the title Hide the hover overlay until velcro settles (fix first-frame jump) Compute the hover overlay position from live rects (fix first-frame jump) Jun 5, 2026
@lukemelia lukemelia marked this pull request as ready for review June 5, 2026 04:08
@lukemelia lukemelia requested review from a team and burieberry June 5, 2026 04:08
@lukemelia lukemelia merged commit 7a6633a into main Jun 5, 2026
75 of 101 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants